/*
 * Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sk.dialog;

import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.execution.util.ExecUtil;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ValidationInfo;
import com.sk.action.BrowseAction;
import com.sk.action.GenAction;
import com.sk.action.ScriptAction;
import com.sk.utils.FileUtil;
import com.sk.utils.GenNotification;
import org.apache.http.util.TextUtils;
import org.jetbrains.annotations.Nullable;

import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.KeyStroke;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * GenerateDialogPane生成工具主界面
 *
 * @author: xudong
 * @see: select generate dialog
 * @version: v1.0.0
 * @since 2022-02-21
 */
public class GenerateDialogPane extends JDialog {
    private static final Logger LOG = Logger.getInstance(GenerateDialogPane.class);
    private static final String COMMAND_STATEMENT = "add_library(napitest SHARED x_napi_tool.cpp napitest.cpp "
            + "napitest_middle.cpp)" + FileUtil.getNewline() + "target_link_libraries(napitest libace_napi.z.so)";
    private static final String REGEX = "napitest";
    private static final Pattern LF_PATTERN = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

    private JPanel contentPane;
    private JTextField interPath;
    private JTextField genPath;
    private JTextField scriptPath;
    private JButton selectInter;
    private JButton selectGenPath;
    private JButton selectScriptPath;
    private JLabel interText;
    private JLabel genText;
    private JLabel scriptText;
    private boolean generateSuccess = true;
    private String sErrorMessage = "";
    private String interFileOrDir;
    private String genOutDir;
    private String scriptOutDir;
    private final Project project;


    /**
     * 构造函数
     *
     * @param project       projectid
     * @param interFilePath 接口文件路径
     * @param genDir        生成框架文件路径
     * @param scriptDir     脚本目录
     */
    public GenerateDialogPane(Project project, String interFilePath, String genDir, String scriptDir) {
        this.project = project;
        this.interFileOrDir = interFilePath;
        this.genOutDir = genDir;
        this.scriptOutDir = scriptDir;

        interText.setText("接口文件:");
        genText.setText("生成框架路径:");
        scriptText.setText("编译脚本路径:");

        interPath.setText(interFileOrDir);
        genPath.setText(genOutDir);
        scriptPath.setText(genOutDir);

        // call onCancel() on ESCAPE
        contentPane.registerKeyboardAction(actionEvent -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        BrowseAction browseAction = new BrowseAction(project, selectInter, interPath, genPath, scriptPath);
        selectInter.addActionListener(browseAction);
        selectGenPath.addActionListener(new GenAction(selectGenPath, genPath));
        selectScriptPath.addActionListener(new ScriptAction(selectScriptPath, scriptPath));
    }

    @Override
    public synchronized void addWindowListener(WindowListener windowListener) {
        super.addWindowListener(windowListener);
        new WindowAdapter() {
            /**
             * close dialog
             *
             * @param windowEvent WindowEvent
             */
            @Override
            public void windowClosing(WindowEvent windowEvent) {
                onCancel();
            }
        };
    }

    /**
     * 验证文本选择框是否空。是否替换已存在的内容
     *
     * @return ValidationInfo 返回不符要求的信息。
     */
    @Nullable
    public ValidationInfo validationInfo() {
        String fileInter = interPath.getText();
        String scriptDir = scriptPath.getText();
        String filegypDir = genPath.getText();
        boolean isEmptyFile = TextUtils.isEmpty(fileInter)
                || TextUtils.isEmpty(scriptDir)
                || TextUtils.isEmpty(filegypDir);

        ValidationInfo validationInfo = null;
        if (isEmptyFile) {
            String warnMsg = "接口文件、框架、编译脚本路径不能为空";
            warningMessage(warnMsg);
            validationInfo = new ValidationInfo(warnMsg);
            return validationInfo;
        }

        File file = new File(filegypDir + "/binding.gyp");
        if (file.exists()) {
            ConfirmDialog confirmDialog = new ConfirmDialog("是否替换已存在的编译脚本?");
            if (!confirmDialog.showAndGet()) {
                validationInfo = new ValidationInfo(String.format("不替换现有编译脚本：%s", file));
                return validationInfo;
            }
        }
        return validationInfo;
    }

    private void onCancel() {
        dispose();
    }

    private void warningMessage(String title) {
        String notiContent = "请选择接口文件或文件夹，生成框架路径，编译脚本路径";
        GenNotification.notifyMessage(this.project, notiContent, title, NotificationType.WARNING);
    }

    /**
     * 执行主程序入口
     *
     * @return 执行状态
     */
    public boolean runFun() throws IOException, ExecutionException {
        GenNotification.notifyMessage(this.project, "", "正在生成", NotificationType.INFORMATION);
        interFileOrDir = interPath.getText();
        genOutDir = genPath.getText();
        scriptOutDir = scriptPath.getText();


        List<String> params = new ArrayList<>(Arrays.asList(genInArgs().split(" ")));
        params.add("-o");
        params.add(scriptOutDir);

        String execPath = genCommand();

        if (callExtProcess(execPath, params)) {
            GenNotification.notifyMessage(project, genPath.getText(), "提示", NotificationType.INFORMATION, true);
            return true;
        } else {
            GenNotification.notifyMessage(project, sErrorMessage, "错误", NotificationType.ERROR);
        }

        return false;
    }

    /**
     * 生成命令行指令
     *
     * @return 返回命令行执行内容
     */
    private String genCommand() {

        String sysName = System.getProperties().getProperty("os.name").toUpperCase();
        String tmpDirFile = System.getProperty("java.io.tmpdir");
        String srcGenerator;
        if (sysName.contains("WIN")) {
            srcGenerator = "cmds/win/napi_generator-win.exe";
            tmpDirFile += "napi_generator-win.exe";
        } else if (sysName.contains("LINUX")) {
            srcGenerator = "cmds/linux/napi_generator-linux";
            tmpDirFile += "napi_generator-linux";
        } else {
            srcGenerator = "cmds/mac/napi_generator-macos";
            tmpDirFile += "napi_generator-macos";
        }
        File file = new File(tmpDirFile);
        if (!file.exists()) {
            try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(srcGenerator);
                 FileOutputStream fw = new FileOutputStream(file)) {

                byte[] bs = new byte[1024];

                boolean isNewFile = file.createNewFile();
                if (!isNewFile) {
                    LOG.info("writeTmpFile createNewFile error");
                }
                int byteLenth;
                while ((byteLenth = inputStream.read(bs)) > 0) {
                    fw.write(bs, 0, byteLenth);
                }

                if ((sysName.contains("LINUX") || sysName.contains("MAC OS")) && !executable(tmpDirFile)) {
                    GenNotification.notifyMessage(project, sErrorMessage, "chmod 出错", NotificationType.ERROR);
                    tmpDirFile = "";
                }
            } catch (ExecutionException | IOException exception) {
                LOG.error(exception.getMessage());
                tmpDirFile = "";
            }
        }
        return tmpDirFile;
    }

    /**
     * 生成 -f -d 输入参数。
     *
     * @return 生成后的值-f -d的值
     */
    private String genInArgs() {

        String[] interArr = interFileOrDir.split(",");
        StringBuilder tsParam = new StringBuilder("-f ");
        StringBuilder dirParam = new StringBuilder("-d ");

        String inputCommand = "";
        if (interArr.length > 0) {
            for (String interStr : interArr) {
                File interFile = new File(interStr);
                if (interFile.isDirectory()) {
                    dirParam.append(interStr).append(" ");
                } else {
                    tsParam.append(interStr).append(",");
                }
            }
            if (!TextUtils.isEmpty(tsParam.toString().replaceAll("-f", ""))
                    && !TextUtils.isBlank(tsParam.toString().replaceAll("-f", ""))) {
                inputCommand += tsParam.substring(0, tsParam.length() - 1);
            }
            if (!TextUtils.isEmpty(dirParam.toString().replace("-d", ""))
                    && !TextUtils.isBlank(dirParam.toString().replace("-d", ""))) {
                inputCommand += dirParam.substring(0, dirParam.length() - 1);
            }
        }


        return inputCommand;
    }

    private boolean callExtProcess(String execPath, List<String> params) throws  ExecutionException {

        if (params.size() < 1 || execPath.isEmpty()) {
            GenNotification.notifyMessage(this.project, "执行命令文件为空", "空命令行提示", NotificationType.ERROR);
            return false;
        }

        GeneralCommandLine commandLine = new GeneralCommandLine();
        commandLine.setExePath(execPath);
        commandLine.addParameters(params);
        commandLine.setCharset(Charset.defaultCharset());
        ProcessOutput processOutput = ExecUtil.execAndGetOutput(commandLine);

        if (processOutput.getExitCode() == 0) {
            writeCommand();
        } else {
            sErrorMessage = processOutput.getStderr();
            return false;
        }
        return true;
    }

    /**
     * 写makeFile.txt文件
     */
    private void writeCommand() {
        FileUtil fileUtil = new FileUtil();
        String filePath = fileUtil.makeFile(genOutDir + "/makeFile.txt");
        if (TextUtils.isEmpty(filePath)) {
            LOG.info("makeFile is fail");
            return;
        }
        Matcher matcher = LF_PATTERN.matcher(COMMAND_STATEMENT);
        String statement = matcher.replaceAll(scriptOutDir);
        try {
            if (!fileUtil.findStringInFile(filePath, statement)) {
                fileUtil.writeErrorToTxt(filePath, statement);
            }
        } catch (IOException ioException) {
            LOG.error("writeCommand io error" + ioException);
        }
    }

    /**
     * 赋值可执行文件权限。
     *
     * @param generatorPath 可执行文件路径
     * @throws IOException          打开文件异常
     * @throws InterruptedException 中断异常
     */
    private boolean executable(String generatorPath) throws ExecutionException {
        List<String> params = new ArrayList<>();
        params.add("a+x");
        params.add(generatorPath);
        return callExtProcess("chmod", params);
    }


    /**
     * 获取生成成功结果文件。
     *
     * @param process 进程ID
     */
    private void genResultLog(Process process) {
        BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        String sErr;
        String sOut;
        sErr = getErrorResult(stdError);
        if (TextUtils.isEmpty(sErr)) {
            sOut = genInputLog(stdInput);
            if (!generateIsSuccess(sOut)) {
                sErrorMessage = sOut;
            }
            return;
        }
        generateSuccess = false;
        sErrorMessage = sErr;
    }

    /**
     * 获取生成失败结果文件。
     *
     * @param stdError error buff
     * @return ErrorResult
     */
    private String getErrorResult(BufferedReader stdError) {
        StringBuilder sErr = new StringBuilder();
        while (true) {
            String sTmp;
            try {
                if ((sTmp = stdError.readLine()) == null) {
                    break;
                }
                sErr.append(sTmp).append(FileUtil.getNewline());
            } catch (IOException ioException) {
                LOG.error(" genResultLog stdInput error" + ioException);
            }
        }
        return sErr.toString();
    }

    private boolean generateIsSuccess(String sOut) {
        generateSuccess = sOut.contains("success") || TextUtils.isEmpty(sOut);
        return generateSuccess;
    }

    /**
     * 获取生成文本内容。
     *
     * @param stdInput input buff
     * @return 返回当前输入框内容
     */
    private String genInputLog(BufferedReader stdInput) {
        StringBuilder sOut = new StringBuilder();
        while (true) {
            String sTmp;
            try {
                if ((sTmp = stdInput.readLine()) == null) {
                    break;
                }
                sOut.append(sTmp).append(FileUtil.getNewline());
            } catch (IOException ioException) {
                LOG.error(" genResultLog stdInput error" + ioException);
            }
        }
        return sOut.toString();
    }


    JPanel getContentPanel() {
        return contentPane;
    }
}